package com.linkedin.dagli.generator;

import com.linkedin.dagli.producer.Producer;
import java.lang.reflect.Type;
import java.util.Objects;


/**
 * A {@link Generator} that always produces a specific value.
 *
 * @param <R> the type of the generated constant value.
 */
public final class Constant<R> extends AbstractGenerator<R, Constant<R>> implements Generator<R> {
  private static final long serialVersionUID = 1;

  private static final int HASH_SALT = Constant.class.hashCode();
  private static final Constant<?> NULL_INSTANCE = new Constant<>();

  private R _value;

  @Override
  protected boolean hasAlwaysConstantResult() {
    return true; // unsurprisingly, Constants are constant
  }

  /**
   * Returns a {@link Constant} that always produces null values.
   *
   * The {@link Constant} returned will always be the same instance.
   *
   * @param <R> the type of null to be generated
   * @return a canonical instance of {@link Constant} that produces nulls
   */
  @SuppressWarnings("unchecked") // nulls can assume any object type
  public static <R> Constant<R> nullValue() {
    return (Constant<R>) NULL_INSTANCE;
  }

  /**
   * Returns a new Constant with a null value.
   */
  public Constant() {
    // all Constants with a value of null can share the same fixed UUID
    super(0x94128978285d1c11L, 0x2bb9e429f20196bdL);
    _value = null;
  }

  /**
   * Returns a {@link Constant} that will generate the specified value.
   *
   * @param value the value to be generated by the {@link Constant} instance
   * @return a {@link Constant} that will generate the specified value
   */
  public Constant<R> withValue(R value) {
    if (value == null) {
      return nullValue();
    } else {
      return new Constant<>(value);
    }
  }

  /**
   * Gets the {@link Constant}'s value; this is the result that will be "generated" by this instance.
   *
   * @return the {@link Constant}'s value
   */
  public R getValue() {
    return _value;
  }

  /**
   * Creates a new Constant that will generate the provided value.  Note that this specific instance will be
   * "generated", not clones.
   *
   * @param value the instance value that should be generated.
   */
  public Constant(R value) {
    _value = value;
  }

  @Override
  public R generate(long index) {
    return _value;
  }

  @Override
  protected boolean computeEqualsUnsafe(Constant other) {
    return Objects.equals(this._value, other._value);
  }

  @Override
  protected int computeHashCode() {
    return Objects.hashCode(_value) + HASH_SALT;
  }

  private String valueAsString() {
    // add quotes around strings to avoid possible confusion for human readers
    return _value instanceof CharSequence ? "\"" + _value + "\"" : Objects.toString(_value);
  }

  @Override
  protected String getDefaultName() {
    return "Constant = " + valueAsString();
  }

  @Override
  protected String getDefaultShortName() {
    return valueAsString();
  }

  /**
   * If a provided producer is a {@link Constant}, returns its value; otherwise returns null.
   *
   * This is a convenience method, equivalent to:
   * {@code return maybeConstant instanceof Constant ? ((Constant<T>) maybeConstant).getValue() : null}
   *
   * @param maybeConstant a producer that may or may not be a {@link Constant}
   * @return if a provided producer is a {@link Constant}, the constant's value; otherwise null
   */
  public static <T> T tryGetValue(Producer<T> maybeConstant) {
    return maybeConstant instanceof Constant ? ((Constant<T>) maybeConstant).getValue() : null;
  }

  @Override
  protected Type getResultSupertype() {
    return _value == null ? super.getResultSupertype() : _value.getClass();
  }
}
